/**
 * MAC.java
 */
package org.dicr.util.net;

import java.io.*;

/**
 * MAC-address. The physical address of network interface card (NIC).
 *
 * @author Igor A Tarasov, &lt;java@dicr.org&gt;
 * @version 060125
 */
public class MAC implements Cloneable, Serializable, Comparable<MAC> {

	private static final long serialVersionUID = 1L;

	/** string representation format */
	public static final String FORMAT = "%02X:%02X:%02X:%02X:%02X:%02X"; //$NON-NLS-1$

	/** length in bytes */
	public static final int LENGTH = 6;

	/** Octets divider */
	public static final char DIV = ':';

	/** MAC value */
	protected long value = 0;

	/**
	 * Constructor
	 *
	 * @param aValue
	 * @throws IncorrectAddressException
	 */
	public MAC(final long aValue) throws IncorrectAddressException {
		super();
		this.setValue(aValue);
	}

	/**
	 * Constructor
	 *
	 * @param aValue
	 * @throws IncorrectAddressException
	 */
	public MAC(final byte[] aValue) throws IncorrectAddressException {
		super();
		if (aValue == null) throw new IllegalArgumentException("null value"); //$NON-NLS-1$
		this.setValue(MAC.parse(aValue));
	}

	/**
	 * Constructor
	 *
	 * @param aValue
	 * @throws IncorrectAddressException
	 */
	public MAC(final String aValue) throws IncorrectAddressException {
		super();
		if (aValue == null) throw new IllegalArgumentException("null value"); //$NON-NLS-1$
		this.setValue(MAC.parse(aValue));
	}

	protected void setValue(final long aValue) throws IncorrectAddressException {
		MAC.check(aValue);
		this.value = aValue;
	}

	/**
	 * Return long value.
	 *
	 * @return long
	 */
	public long toLong() {
		return this.value;
	}

	/**
	 * Return bytes value
	 *
	 * @return byte[6]
	 */
	public byte[] toBytes() {
		byte[] ret = null;
		try {
			ret = MAC.toBytes(this.value);
		} catch (final IncorrectAddressException ex) {
			throw new Error(ex);
		}
		return ret;
	}

	/**
	 * Return String representation value
	 *
	 * @return string mac (XX:XX:XX:XX:XX:XX)
	 */
	@Override
	public String toString() {
		String ret = null;
		try {
			ret = MAC.toString(this.value);
		} catch (final IncorrectAddressException ex) {
			throw new Error(ex);
		}
		return ret;
	}

	/**
	 * Check if value can be MAC-address. MAC-address consists of six bytes, so
	 * two high bytes of long value must be
	 * zero.
	 *
	 * @param mac
	 * @throws IncorrectAddressException
	 */
	public static void check(final long mac) throws IncorrectAddressException {
		if ((mac & 0xFFFF000000000000L) != 0) throw new IncorrectAddressException(String.format("mac: %06X", //$NON-NLS-1$
				Long.valueOf(mac)));
	}

	/**
	 * Parse value from bytes.
	 *
	 * @param bytes
	 *            byte[6]
	 * @return mac
	 * @throws IncorrectAddressException
	 */
	public static long parse(final byte[] bytes) throws IncorrectAddressException {
		if (bytes == null) throw new IllegalArgumentException("null bytes"); //$NON-NLS-1$
		if (bytes.length != MAC.LENGTH) throw new IncorrectAddressException("bytes length != 6"); //$NON-NLS-1$
		long value = 0;
		for (int i = 0; i < 6; i++) {
			value = value << 8 | bytes[i] & 0x00FF;
		}
		return value;
	}

	/**
	 * Parse value from String. String representation of mac-address must be in
	 * form of hex digits, for example:
	 * 00:30:4F:25:EE:3C
	 *
	 * @param value
	 *            string value
	 * @return value
	 * @throws IncorrectAddressException
	 *             incorrect value.
	 */
	public static long parse(final String value) throws IncorrectAddressException {
		if (value == null) throw new IllegalArgumentException("null value"); //$NON-NLS-1$
		long lmac = 0;
		try {
			final char[] chars = value.toCharArray();
			// 1
			int pos = 0;
			int len = 0;
			while (pos + len < chars.length && chars[pos + len] != MAC.DIV) {
				len++;
			}
			if (pos + len >= chars.length || chars[pos + len] != MAC.DIV || len < 1 || len > 2) {
				throw new IncorrectAddressException(value);
			}
			int dec = Integer.parseInt(new String(chars, pos, len), 16);
			if (dec < 0 || dec > 255) throw new IncorrectAddressException(value);
			lmac = dec;
			// 2
			pos += len + 1;
			len = 0;
			while (pos + len < chars.length && chars[pos + len] != MAC.DIV) {
				len++;
			}
			if (pos + len >= chars.length || chars[pos + len] != MAC.DIV || len < 1 || len > 2) {
				throw new IncorrectAddressException(value);
			}
			dec = Integer.parseInt(new String(chars, pos, len), 16);
			if (dec < 0 || dec > 255) throw new IncorrectAddressException(value);
			lmac = lmac << 8 | dec;
			// 3
			pos += len + 1;
			len = 0;
			while (pos + len < chars.length && chars[pos + len] != MAC.DIV) {
				len++;
			}
			if (pos + len >= chars.length || chars[pos + len] != MAC.DIV || len < 1 || len > 2) {
				throw new IncorrectAddressException(value);
			}
			dec = Integer.parseInt(new String(chars, pos, len), 16);
			if (dec < 0 || dec > 255) throw new IncorrectAddressException(value);
			lmac = lmac << 8 | dec;
			// 4
			pos += len + 1;
			len = 0;
			while (pos + len < chars.length && chars[pos + len] != MAC.DIV) {
				len++;
			}
			if (pos + len >= chars.length || chars[pos + len] != MAC.DIV || len < 1 || len > 2) {
				throw new IncorrectAddressException(value);
			}
			dec = Integer.parseInt(new String(chars, pos, len), 16);
			if (dec < 0 || dec > 255) throw new IncorrectAddressException(value);
			lmac = lmac << 8 | dec;
			// 5
			pos += len + 1;
			len = 0;
			while (pos + len < chars.length && chars[pos + len] != MAC.DIV) {
				len++;
			}
			if (pos + len >= chars.length || chars[pos + len] != MAC.DIV || len < 1 || len > 2) {
				throw new IncorrectAddressException(value);
			}
			dec = Integer.parseInt(new String(chars, pos, len), 16);
			if (dec < 0 || dec > 255) throw new IncorrectAddressException(value);
			lmac = lmac << 8 | dec;
			// 6
			pos += len + 1;
			len = 0;
			while (pos + len < chars.length) {
				len++;
			}
			if (pos + len != chars.length || len < 1 || len > 2) {
				throw new IncorrectAddressException(value);
			}
			dec = Integer.parseInt(new String(chars, pos, len), 16);
			if (dec < 0 || dec > 255) throw new IncorrectAddressException(value);
			lmac = lmac << 8 | dec;
		} catch (final NumberFormatException ex) {
			throw new IncorrectAddressException(value, ex);
		}
		return lmac;
	}

	/**
	 * Convert to bytes.
	 *
	 * @param macvalue
	 * @return byte[6]
	 * @throws IncorrectAddressException
	 */
	public static byte[] toBytes(final long macvalue) throws IncorrectAddressException {
		MAC.check(macvalue);
		long value = macvalue;
		final byte[] bytes = new byte[6];
		for (int i = 0; i < 6; i++) {
			bytes[5 - i] = (byte) (value & 0x0FF);
			value >>= 8;
		}
		return bytes;
	}

	/**
	 * Convert to string representation.
	 *
	 * @param value
	 *            value
	 * @return string value in form XX:XX:XX:XX:XX:XX
	 * @throws IncorrectAddressException
	 *             incorrect address value.
	 */
	public static String toString(final long value) throws IncorrectAddressException {
		MAC.check(value);
		final long b1 = value >> 40 & 0x0FF;
		final long b2 = value >> 32 & 0x0FF;
		final long b3 = value >> 24 & 0x0FF;
		final long b4 = value >> 16 & 0x0FF;
		final long b5 = value >> 8 & 0x0FF;
		final long b6 = value & 0x0FF;
		return String.format(MAC.FORMAT, Long.valueOf(b1), Long.valueOf(b2), Long.valueOf(b3), Long.valueOf(b4),
				Long.valueOf(b5), Long.valueOf(b6));
	}

	/**
	 * @see java.lang.Object#clone()
	 */
	@SuppressWarnings("javadoc")
	@Override
	public Object clone() throws CloneNotSupportedException {
		final MAC mac = (MAC) super.clone();
		mac.value = this.value;
		return mac;
	}

	/**
	 * Compare.
	 *
	 * @param o
	 * @return -1 (<), 0 (=), +1 (>)
	 */
	@Override
	public int compareTo(final MAC o) {
		if (this.value > o.value) return 1;
		else if (this.value < o.value) return -1;
		return 0;
	}

	/**
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int PRIME = 31;
		int result = 1;
		result = PRIME * result + (int) (this.value ^ this.value >>> 32);
		return result;
	}

	/**
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(final Object obj) {
		if (this == obj) return true;
		if (obj == null) return false;
		if (this.getClass() != obj.getClass()) return false;
		final MAC other = (MAC) obj;
		if (this.value != other.value) return false;
		return true;
	}

}
